#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import os
import json
import configparser
import threading
import time
import http
import platform

from common.cleaner import clear
from common.env_common import get_install_head_path
from oracle.common.constants import ScriptExitCode
from oracle.common.device_manager.constants import AgentScene, ReturnCode, \
    StorageNoNeedRetryErrorCode, StorageErrorCode
from oracle.common.device_manager.device_info import DeviceInfo, SessionInfo
from oracle.common.device_manager.https_request import HttpRequest, RequestUtils
from oracle import logger

DMA_PROXY = 'http://protectengine-e-dma:30071'
storage_session_lock = threading.Lock()

if platform.system().lower() == "windows":
    AGENT_CONF_PATH = f"{get_install_head_path()}/DataBackup/ProtectClient/Plugins/GeneralDBPlugin/confhcpconf.ini"
else:
    AGENT_CONF_PATH = f"{get_install_head_path()}/DataBackup/ProtectClient/Plugins/GeneralDBPlugin/conf/hcpconf.ini"


class StorageBase:
    def __init__(self, device_info: DeviceInfo):
        self.init_success = False
        self.error_code = 0
        self.session_info = SessionInfo()
        self.storage_ip = device_info.ip
        self.storage_port = device_info.port
        self.storage_username = device_info.username
        self.storage_password = device_info.password
        self.retry_times = 3
        self.retry_interval_time_sec = 30
        self.agent_scene = self._get_agent_scene()
        self.cert_enable = device_info.cert_enable
        self.cert_path = device_info.cert_path
        self.crl_path = device_info.crl_path
        self.retry_flag = device_info.retry_flag
        if self._login():
            self.init_success = True

    def __del__(self):
        self._logout()
        if self.init_success:
            clear(self.storage_password)
        self.init_success = False
        logger.info(f'storage session logout, username: {self.storage_username}')

    @staticmethod
    def _get_agent_scene():
        if not os.path.isfile(AGENT_CONF_PATH):
            return AgentScene.EXTERNAL
        read_ini = configparser.ConfigParser()
        read_ini.read(AGENT_CONF_PATH)
        value = read_ini.get("NasPlugInConfig", "AGENT_PLUGIN_USAGE_SCENE")
        if int(value) < len(list(AgentScene)):
            return AgentScene(int(value))
        return AgentScene.EXTERNAL

    @staticmethod
    def _check_response(rsp):
        if not isinstance(rsp, Exception):
            if rsp.get("error", {}).get("code", None) is None and rsp.get("result", {}).get("code", None) is None:
                error_desc = f'Storage response json format error, without error or error.code'
                return ReturnCode.FAILED, error_desc
            else:
                error_code = ReturnCode.SUCCESS
                error_desc = ''
                if rsp.get("error", {}).get('code'):
                    error_code = int(rsp.get('error').get('code'))
                    error_desc = rsp.get('error').get('description')
                elif rsp.get("result", {}).get('code'):
                    error_code = int(rsp.get('result').get('code'))
                    error_desc = rsp.get('result').get('description')
                return error_code, error_desc
        else:
            return ReturnCode.FAILED, f'Storage response exception!'

    def _login(self):
        if not self.storage_ip or not self.storage_port:
            logger.error(f'login failed, storage ip: {self.storage_ip}, storage port: {self.storage_port}')
            return False
        if not self.storage_username or not self.storage_password:
            logger.error(f'login failed, username: {self.storage_username} or pwd is null')
            return False
        req = HttpRequest()
        req.method = 'POST'
        req.host = self.storage_ip
        req.port = self.storage_port
        req.suffix = '/deviceManager/rest/xxxxx/sessions'
        req.body = json.dumps({
            'username': self.storage_username,
            'password': self.storage_password,
            'scope': 0
        })
        req.https = True
        req.verify = self.cert_enable
        proxy_url = self._get_proxy()
        req.proxy = proxy_url if proxy_url else None
        try:
            request = RequestUtils(req, self.cert_path, self.crl_path, self.retry_flag)
            status_code, rsp = request.send_request()
            if status_code != http.HTTPStatus.OK:
                logger.error(f'The storage session login failed, status code:{status_code}.')
                return False
            error_code, error_desc = self._check_response(rsp)
            if error_code == ReturnCode.SUCCESS:
                self.session_info.token = rsp.get('data', {}).get('iBaseToken')
                self.session_info.device_id = rsp.get('data', {}).get('deviceid')
                if request.get_headers():
                    self.session_info.cookie = request.get_headers().get('Set-Cookie')
                if not self.session_info.token or not self.session_info.device_id or not self.session_info.cookie:
                    logger.error(f'The storage session login failed, token, device_id or cookie is null.')
                    return False
                logger.info(f'Login storage session success')
                return True
            else:
                logger.error(f'The storage session login failed, error code:{error_code}, error desc:{error_desc}')
                return False
        except Exception as e:
            logger.error(f'Fail to login storage! exception={e}', exc_info=True)
            if 'CRL signature failure' in e.__str__():
                self.error_code = ScriptExitCode.ERROR_CRL_SIGNATURE_FAILURE
            elif 'certificate revoked' in e.__str__():
                self.error_code = ScriptExitCode.ERROR_CERT_REVOKE
            elif 'CERTIFICATE_VERIFY_FAILED' in e.__str__():
                self.error_code = ScriptExitCode.ERROR_INVALID_CERT
            return False

    def _logout(self):
        if not self.init_success:
            return ReturnCode.SUCCESS, ''
        req = HttpRequest()
        req.method = 'DELETE'
        req.suffix = '/sessions'
        req.https = True
        rsp = self._send_request(req)
        return self._check_response(rsp)

    def _send_request(self, req: HttpRequest, lock_session=False, url=None):
        # 增加storage消息重试
        rsp = {}
        retry_num = 0
        suffix_temp = req.suffix
        req.host = self.storage_ip
        req.port = self.storage_port
        while retry_num < self.retry_times:
            # 增加特定资源访问锁
            if lock_session:
                storage_session_lock.acquire()
            network_error = False
            try:
                rsp = self._send_request_direct(req, suffix_temp, url)
            except Exception as e:
                logger.error(f'Network error, will try. ')
                network_error = True
                rsp = e
            if lock_session:
                storage_session_lock.release()
            if network_error:
                retry_num += 1
                continue
            # 如果是网络原因，http.session已经设置了重试次数。
            # 此处检测storage返回错误码时，根据该错误码判断是否需要重试
            error_code, error_desc = self._check_response(rsp)
            if (error_code == ReturnCode.SUCCESS) or (error_code in StorageNoNeedRetryErrorCode) or \
                    error_code == ReturnCode.FAILED:
                # storage明确返回SUCCESS、FAILED、或不需要重试的错误码，则退出重试机制
                break
            time.sleep(self.retry_interval_time_sec)
            retry_num += 1
            logger.debug(f'Already send request to storage for try num:{retry_num}')
        return rsp

    def _send_request_direct(self, req: HttpRequest, suffix_temp, url=None):
        try:
            if not self.init_success:
                msg = f'Login storage init not success, Please Check whether the host:{req.host}, port:{req.port}, ' \
                      f'user name:{self.storage_username} and password are correct'
                raise Exception(msg)
            req.https = True
            if not url:
                req.suffix = f'/deviceManager/rest/{self.session_info.device_id}{suffix_temp}'
            else:
                req.suffix = url
            req.cookie = self.session_info.cookie
            req.headers = {'iBaseToken': self.session_info.token, 'Content-Type': 'application/json'}
            req.verify = False
            proxy_url = self._get_proxy()
            req.proxy = proxy_url if proxy_url else None
            request = RequestUtils(req, self.cert_path, self.crl_path)
            status_code, rsp = request.send_request()
            if status_code != http.HTTPStatus.OK:
                msg = f'Send request in else failed, status code:{status_code}.'
                raise Exception(msg)
            error_code = rsp.get("error", {}).get('code')
            if error_code in (StorageErrorCode.UNAUTH, StorageErrorCode.NO_USER_PERMISSION,
                              StorageErrorCode.AUTH_IP_INCONSISTENCY) and self._login():
                request = RequestUtils(req, self.cert_path, self.crl_path)
                status_code, rsp = request.send_request()
                if status_code != http.HTTPStatus.OK:
                    msg = f'Send request in else failed, status code:{status_code}.'
                    raise Exception(msg)
            return rsp
        except Exception as request_e:
            return request_e

    def _get_proxy(self):
        if self.agent_scene == AgentScene.EXTERNAL:
            return ''
        return DMA_PROXY
